package com.miyake.loveviewweb.server.service;

import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.slim3.datastore.Datastore;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.miyake.loveviewweb.client.account.LoginInfo;
import com.miyake.loveviewweb.client.service.DataService;
import com.miyake.loveviewweb.client.service.InvalidPluginException;
import com.miyake.loveviewweb.server.ChartModelManager;
import com.miyake.loveviewweb.server.DataColumnConverter;
import com.miyake.loveviewweb.server.DataModelManager;
import com.miyake.loveviewweb.server.PluginManager;
import com.miyake.loveviewweb.server.ValueConverter;
import com.miyake.loveviewweb.server.meta.ChartModelMeta;
import com.miyake.loveviewweb.server.meta.DataModelMeta;
import com.miyake.loveviewweb.server.meta.FolderModelMeta;
import com.miyake.loveviewweb.server.meta.GroupModelMeta;
import com.miyake.loveviewweb.server.meta.UserModelMeta;
import com.miyake.loveviewweb.shared.XsvToDataColumnModel;
import com.miyake.loveviewweb.shared.model.ChartModel;
import com.miyake.loveviewweb.shared.model.ChartModel.ChartEngine;
import com.miyake.loveviewweb.shared.model.DataModel;
import com.miyake.loveviewweb.shared.model.BinaryModel;
import com.miyake.loveviewweb.shared.model.FolderModel;
import com.miyake.loveviewweb.shared.model.GroupModel;
import com.miyake.loveviewweb.shared.model.ImageDataBodyModel;
import com.miyake.loveviewweb.shared.model.PluginModel;
import com.miyake.loveviewweb.shared.model.UserModel;
import com.miyake.loveviewweb.shared.model.ValueModel;
import com.miyake.rest.ServiceType;
import com.miyake.rest.WebServiceProperty;
import com.miyake.rest.XmlSerializer;
import com.miyake.rest.comm.BinaryHttpPost;
import com.miyake.rest.comm.XmlHttpPost;


public class DataServiceImpl implements DataService {
    
    public FolderModel createRootFolder(String name) {
        Key key = Datastore.allocateId(MyUserKey.get(), FolderModel.class);
        FolderModel folder = new FolderModel();
        folder.setKey(key);
        folder.setOwner(MyUserKey.get());
        folder.setFolderName(name);
        Datastore.put(folder);
        return folder;
    }

    private String getUserId() {
        UserService userService = UserServiceFactory.getUserService();
        User u = userService.getCurrentUser();
        return u.getUserId();
    }

    public FolderModel getFolder(Key key) {
        return Datastore.get(FolderModel.class, key);
    }

    public List<FolderModel> getRootFolders() {
        FolderModelMeta e = FolderModelMeta.get();
        List<FolderModel> list = Datastore.query(e).filter(e.owner.equal(MyUserKey.get())).filter(e.parent.equal(null)).asList();
        return list;
    }

    public FolderModel createFolder(String name, Key parentKey) {
        FolderModel folder = new FolderModel();
        folder.setFolderName(name);
        folder.setParent(parentKey);
        folder.setOwner(MyUserKey.get());
        Datastore.put(folder);
        return folder;
    }

    public List<FolderModel> getSubFolders(Key key) {
        FolderModelMeta e = FolderModelMeta.get();
        List<FolderModel> list = Datastore.query(e).filter(e.parent.equal(key)).asList();
        return list;
    }

    public DataModel getData(Key key, boolean content) {
        DataModel dataModel = Datastore.get(DataModel.class, key);
        dataModel.setContent(content);
        DataModel ret = null;
        if (content) {
            ret = new DataModelManager().fillDataModel(dataModel, true);
        }
        else {
            ret = new DataModelManager().fillDataModel(dataModel, false);
        }
        return ret;
    }

    public List<Key> getDataKeyList(Key folderKey) {
        DataModelMeta e = DataModelMeta.get();
        return Datastore.query(e).filter(e.folder.equal(folderKey)).asKeyList();
    }

    public List<DataModel> getDataList(Key folderKey, boolean content) {
        DataModelMeta e = DataModelMeta.get();
        List<Key> keys = Datastore.query(e).filter(e.folder.equal(folderKey)).asKeyList();
        List<DataModel> ret = new ArrayList<DataModel>();
        for (Key key : keys) {
            ret.add(getData(key, content));
        }
        return ret;
    }

    public DataModel createData(Key folderKey, String name, String text) {
        FolderModel folder = Datastore.get(FolderModel.class, folderKey);
        Key childKey = Datastore.allocateId(folder.getKey(), DataModel.class);
        
        DataModel data = new DataModel();
        data.setOwner(MyUserKey.get());
        data.setName(name);
        data.setKey(childKey);
        data.setFolder(folder.getKey());
        data.setSourceType(DataModel.SourceType.Manual);
        data.setParsed(true);
        data.setDataType(DataModel.DataType.ValueArray);
        
        try {
            return new DataModelManager().storeXY(data, new XsvToDataColumnModel(text, true).getDataColumnModels());
        } catch (Exception e) {
            return null;
        }      
    }

    public void updateData(DataModel data) {
        new DataModelManager().update(data);
    }

    public void setSelectedItem(Key key) {
      UserModelMeta e = UserModelMeta.get();
      UserModel user = Datastore.query(e).filter(e.userID.equal(this.getUserId())).asSingle();
      user.setSelectedItem(key);
      Datastore.put(user);
    }

    public ChartModel createChart(String name, Key dataKey, Map<String, String> axisDefine, ChartEngine chartEngine, Key pluginKey) {
        ChartModel chartModel = new ChartModel();
        chartModel.setName(name);
        chartModel.setChartEngine(chartEngine);
        chartModel.setChartPluginKey(pluginKey);

        return new ChartModelManager().store(chartModel, dataKey, axisDefine);
    }

    public ChartModel getChart(Key key, boolean content) {
        ChartModel chartModel = Datastore.get(ChartModel.class, key);
        chartModel = buildChartModel(content, chartModel);
        
        return chartModel;
    }

    private ChartModel buildChartModel(boolean content, ChartModel chartModel) {
        return new ChartModelManager().fillChartModel(chartModel, content);
    }

    public List<ChartModel> getChartList(Key dataKey, boolean content) {
        ChartModelMeta e = ChartModelMeta.get();
        if (content) {
            List<ChartModel> ret = new ArrayList<ChartModel>();
            for (ChartModel chartModel : Datastore.query(e).filter(e.parentKey.equal(dataKey)).asList()) {
               ret.add(buildChartModel(content, chartModel));
            }
            return ret;
        }
        else {
            return Datastore.query(e).filter(e.parentKey.equal(dataKey)).asList();
        }
    }

    public void updateChart(ChartModel chartModel) {
        ChartModel chartModel2 = Datastore.get(ChartModel.class, chartModel.getKey());
        chartModel2.setChartWidth(chartModel.getChartWidth());
        chartModel2.setChartHeight(chartModel.getChartHeight());
        Datastore.put(chartModel2);
    }

    public void updateChartsSize(List<Key> keys, Integer width, Integer height) {
        List<ChartModel> charts = Datastore.get(ChartModel.class,keys);
        for (ChartModel chart : charts) {
            chart.setChartHeight(height);
            chart.setChartWidth(width);
            Datastore.put(chart);
        }
    }

    public void updateChartEngine(List<Key> keys, ChartEngine chartEngine, Key pluginKey) {
        List<ChartModel> charts = Datastore.get(ChartModel.class,keys);
        for (ChartModel chart : charts) {
            chart.setChartEngine(chartEngine);
            if (pluginKey != null) {
                chart.setChartPluginKey(pluginKey);
            }
            Datastore.put(chart);
        }
    }

    public void updateChartSize(Key key, Integer width, Integer height) {
        ChartModel chart = Datastore.get(ChartModel.class, key);
        chart.setChartHeight(height);
        chart.setChartWidth(width);
        Datastore.put(chart);
    }

    public void delete(Key key) {
        Datastore.deleteAll(key);
        
        GroupModelMeta e = GroupModelMeta.get();
        List<GroupModel> list = Datastore.query(e).filter(e.publishedData.in(key)).asList();
        for (GroupModel g : list) {
            g.getPublishedData().remove(key);
            Datastore.put(g);
        }
    }

    public List<DataModel> getSubDataList(Key dataKey, boolean content) {
        DataModelMeta e = DataModelMeta.get();
        List<Key> keys =  Datastore.query(e).filter(e.sourceData.equal(dataKey)).asKeyList();
        List<DataModel> ret = new ArrayList<DataModel>();
        for (Key key : keys) {
            ret.add(this.getData(key, content));
        }
        return ret;
    }

    public List<ChartModel> getChartList(List<Key> keys, boolean content) {
        List<ChartModel> charts = Datastore.get(ChartModel.class, keys);
        if (content) {
            for (ChartModel chart : charts) {
                this.buildChartModel(content, chart);
            }
        }
        return charts;
    }

    public void delete(List<Key> keys) {
        for (Key key : keys) {
            Datastore.deleteAll(key);
        }
    }


    public void removePlugin(Key key) {
        // TODO Auto-generated method stub 
    }

    public List<PluginModel> getPluginList(String type, String tag) {
        return PluginManager.getInstance().getList(type);
    }


    public PluginModel getPlugin(Key key) {
        return PluginManager.getInstance().getPlugin(key);
    }

    public Key registerPlugin(String url) throws InvalidPluginException {
        try {
            URL urlObject = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) urlObject.openConnection();
            conn.setRequestMethod("GET");   
            conn.connect();
            
            WebServiceProperty wsProp = XmlSerializer.deserialize(conn.getInputStream(), WebServiceProperty.class);// .fromXml(conn.getInputStream());
            conn.disconnect();
            
            if (!wsProp.getServiceType().equals(ServiceType.NotService)) {
                try {
                    PluginModel pluginModel = PluginManager.getInstance().storePlugin(wsProp);
                    return pluginModel.getKey();
                }
                catch (Exception e) {
                    return null;
                }
            }

            for (String u : wsProp.getRelatedServices()) {
                registerPlugin(u);
            }

            return null;

        }
        catch (Exception e) {
            throw new InvalidPluginException();
        }
    }

    public void clearPlugins() {
        PluginManager.getInstance().clear();
    }

    public List<PluginModel> getMyPluginList(String category, String tag) {
        return PluginManager.getInstance().getMyPlugins(category, tag);
    }

    public void removePlugins(List<Key> keys) {
        PluginManager.getInstance().remove(keys);
    }

    public List<ValueModel> getPluginSetting(Key pluginKey) {
        return PluginManager.getInstance().getSetting(pluginKey);
    }

    public Key executeDataProcessing(final Key pluginKey, final List<ValueModel> values, Map<String, String> data, Key sourceData) {   
        final DataModel source = Datastore.get(DataModel.class, sourceData);
        
        String url = Datastore.get(PluginModel.class, pluginKey).getServiceUrl();
        final WebServiceProperty postProp = PluginManager.getInstance().buildWebServiceProperty(values, data, source);
        
        byte[] rec = new XmlHttpPost(url, postProp.toXML()).getReceivedData();

        WebServiceProperty prop = (WebServiceProperty) WebServiceProperty.fromXml(new String(rec));
        DataModel dataModel = new DataModel();
        dataModel.setSourceType(DataModel.SourceType.Plugin);
        dataModel.setName(prop.getServiceName());
        dataModel.setFolder(source.getFolder());
        dataModel.setSourceData(sourceData);
        dataModel.setPlugin(pluginKey);
        dataModel.setDataType(DataModel.DataType.ValueArray);

        new DataModelManager().storeXY(dataModel, 
            new DataColumnConverter().webToModel(prop.getData()), 
            new ValueConverter().webToModel(prop.getValues()));
                
        return dataModel.getKey();
    }
    

    
    public void parseFile(Key dataKey, Key pluginKey) {
        final DataModel dataModel = Datastore.get(DataModel.class, dataKey);
        PluginModel pluginModel = Datastore.get(PluginModel.class, pluginKey);
        
        BinaryHttpPost post = new BinaryHttpPost(pluginModel.getServiceUrl(), 
            Datastore.get(BinaryModel.class, dataModel.getOriginalData()).getBlobData().getBytes());
        
        WebServiceProperty prop = WebServiceProperty.fromXml(new String(post.getReceivedData()));
                  
        dataModel.setParsed(true);
        dataModel.setOwner(MyUserKey.get());

        dataModel.setPlugin(pluginKey);
        
        new DataModelManager().storeXY(dataModel, 
            new DataColumnConverter().webToModel(prop.getData()),
            new ValueConverter().webToModel(prop.getValues()));
    }

    public void setChartListVisible(Key dataKey, boolean visible) {
        DataModel dataModel = Datastore.get(DataModel.class, dataKey);
        dataModel.setChartListVisible(visible);
        Datastore.put(dataModel);
    }

    public void updateFolder(FolderModel folder) {
        Datastore.put(folder);
    }

    public List<DataModel> getDataList(List<Key> dataKeys, boolean content) {
        List<DataModel> ret = new ArrayList<DataModel>();
        for (Key key : dataKeys) {
            DataModel dataModel = Datastore.getOrNull(DataModel.class, key);
            if (dataModel != null) {
                dataModel= new DataModelManager().fillDataModel(dataModel, content);
                ret.add(dataModel);
            }
        }
        return ret;
    }

    public void updateImageDataBodyModel(ImageDataBodyModel imageDataBodyModel) {
        Datastore.put(imageDataBodyModel);
    }

    public String keyToString(Key key) {
        return Datastore.keyToString(key);
    }

    public Key stringToKey(String key) {
        return Datastore.stringToKey(key);
    }

    public ChartModel getChart(Key key, boolean content, ChartEngine engine) {
        ChartModel chartModel = Datastore.get(ChartModel.class, key);
        chartModel.setChartEngine(engine);
        buildChartModel(true, chartModel);
        return chartModel;
    }

    public void publish(Key contentKey, Key groupKey) {
        GroupModel groupModel = Datastore.get(GroupModel.class, groupKey);
        if (DataModel.class.getName().contains(contentKey.getKind())) {
            DataModel dataModel = Datastore.get(DataModel.class, contentKey);
            groupModel.getPublishedData().add(dataModel.getKey());
            Datastore.put(groupModel);
        }
    }

    public void clearRootFolders() {
        FolderModelMeta e = FolderModelMeta.get();
        List<Key> keys = Datastore.query(e).filter(e.owner.equal(MyUserKey.get())).filter(e.parent.equal(null)).asKeyList();
        Datastore.delete(keys);
    }
    

    public Key createUser(UserModel user) throws Exception {
//        throw new Exception("Cannot create new account now!! Please contact Miyake.");
        UserService userService = UserServiceFactory.getUserService();
        User u = userService.getCurrentUser();
        user.setEmail(u.getEmail());
        user.setUserID(u.getUserId());
        
        UserModelMeta e = UserModelMeta.get();
        List<UserModel> users = Datastore.query(e).filter(e.email.equal(u.getEmail())).asList();
        if (users.size() == 0) {
            Datastore.put(user);
            createRootFolder("Untitled");
        }
        else {
            throw new Exception("Already have account");
        }
        
        return user.getKey();
    }

    public LoginInfo login(String requestUri) {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();
        LoginInfo loginInfo = new LoginInfo();
        
        if(user != null){ // already logged in Google
            loginInfo.setLoggedInGoogle(true);
            UserModelMeta e = UserModelMeta.get();
            UserModel userProp = Datastore.query(e).filter(e.userID.equal(user.getUserId())).asSingle();
            loginInfo.setEmail(user.getEmail());
            loginInfo.setNickname(user.getNickname());
            
            if (userProp != null) { // has an account for this system
                loginInfo.setLoggedInLoveView(true);
                loginInfo.setLogoutUrl(userService.createLogoutURL(requestUri));
            }
            else {// does not have an account for this system
                loginInfo.setLoginUrl(userService.createLoginURL(requestUri));
            }
        }
        else{ // Not yet logged in Google
          loginInfo.setLoginUrl(userService.createLoginURL(requestUri));
        }
        
        return loginInfo;
    }

    public void installPlugins(List<Key> keys) {
        UserModel u = Datastore.get(UserModel.class, MyUserKey.get());
        for (Key key : keys) {
            if (!u.getMyPlugins().contains(key)) {
                u.getMyPlugins().add(key);
            }
        }
        Datastore.put(u);
    }

    public List<PluginModel> getInstalledPlugins(String serviceType) {
        return PluginManager.getInstance().getInstalled(serviceType);
    }


    public void uninstallPlugins(List<Key> keys) {
        PluginManager.getInstance().uninstall(keys);
    }
    
    private <M> void stripUnlinked(List<Key> keys, Class<M> classType) {
        for (Key key : keys) {
            if (Datastore.getOrNull(key)== null) {
                keys.remove(key);
            }
        }   
    }
    
    public List<UserModel> getMyFriends() {
        UserModel me = this.getMyProperty();
        List<Key> friends = me.getMyFriends();
        
        stripUnlinked(friends, UserModel.class);
        return buildUserModel(Datastore.get(UserModel.class, friends));
    }

    public List<UserModel> getAllUsers() {
        UserModelMeta e = UserModelMeta.get();
        return buildUserModel(Datastore.query(e).asList());
    }

    public Key createGroup(String name) {
        GroupModel group = new GroupModel();
        group.setName(name);
        Key me = MyUserKey.get();
        group.getUsers().add(me);
        group.setOwner(me);
        Datastore.put(group);
        return group.getKey();
    }

    public List<GroupModel> getAllGroups() {
        GroupModelMeta e = GroupModelMeta.get();
        List<GroupModel> ret = Datastore.query(e).asList();
        return ret;
    }

    public List<GroupModel> getMyGroups() {
        GroupModelMeta e = GroupModelMeta.get();
        List<GroupModel> ret = Datastore.query(e).filter(e.users.in(MyUserKey.get())).asList();
        return ret;
    }

    public List<UserModel> getUsers(List<Key> keys) {
        return buildUserModel(Datastore.get(UserModel.class, keys));
    }

    private List<UserModel> buildUserModel(List<UserModel> list) {
        for (UserModel userModel : list) {
            buildUserModel(userModel);
        }
        return list;
    }

    public UserModel getUser(Key key) {
        return buildUserModel(Datastore.get(UserModel.class, key));
    }

    private UserModel buildUserModel(UserModel userModel) {
        if (userModel.getImage() != null) {
            userModel.setImageUrl("/userImage?key=" + Datastore.keyToString(userModel.getKey()));
        }
        else {
            userModel.setImageUrl("faceimage.png");
        }
        return userModel;
    }

    public void joinGroup(Key groupKey) {
        GroupModel groupModel = Datastore.get(GroupModel.class, groupKey);
        Key myKey = MyUserKey.get();
        if (!groupModel.getUsers().contains(myKey)) {
            groupModel.getUsers().add(myKey);
            Datastore.put(groupModel);
        }
    }

    public UserModel getMyProperty() {
        return buildUserModel(Datastore.get(UserModel.class, MyUserKey.get()));
    }

    public void updateUser(UserModel userModel) {
        Datastore.put(userModel);
    }

    public void leaveGroup(Key groupKey) {
        GroupModel groupModel = Datastore.get(GroupModel.class, groupKey);
        groupModel.getUsers().remove(MyUserKey.get());
        Datastore.put(groupModel);
    }

    public void deleteUser(Key userKey) {
        Datastore.delete(userKey);
    }

    public void deleteAllUsers() {
        UserModelMeta e = UserModelMeta.get();
        List<Key> keys = Datastore.query(e).asKeyList();
        for (Key key : keys) {
            Datastore.deleteAll(key);
        }
    }

    public Key createUser() throws Exception {
        UserModel userModel = new UserModel();
        return this.createUser(userModel);
    }

    public void addFriend(Key key) {
        UserModel me = getMyProperty();
        if (canAddFriend(key, me)) {
            me.getMyFriends().add(key);
            Datastore.put(me);
        }
    }

    private boolean canAddFriend(Key key, UserModel me) {
        if (me.getMyFriends().contains(key)) {
            return false;
        }
        if (key.equals(me.getKey())) {
            return false;
        }
        return true;
    }

    public void byeFriend(Key key) {
        UserModel me = getMyProperty();
        me.getMyFriends().remove(key);
        Datastore.put(me);
    }

    public void deleteGroup(List<Key> keys) {
        Datastore.delete(keys);
    }

    public void deleteAllGroups() {
        GroupModelMeta e = GroupModelMeta.get();
        Datastore.delete(Datastore.query(e).asKeyList());
    }

    public void setChartColumns(Key dataKey, int columns) {
        DataModel dataModel = Datastore.get(DataModel.class, dataKey);
        dataModel.setChartColumns(columns);
        Datastore.put(dataModel);
    }
}
